#include <efsw/DirWatcherGeneric.hpp>
#include <efsw/FileSystem.hpp>
#include <efsw/Debug.hpp>
#include <efsw/String.hpp>

namespace efsw {

DirWatcherGeneric::DirWatcherGeneric( DirWatcherGeneric * parent, WatcherGeneric * ws, const std::string& directory, bool recursive, bool reportNewFiles ) :
    Parent( parent ),
    Watch( ws ),
    Recursive( recursive ),
    Deleted( false )
{
    resetDirectory( directory );

    if ( !reportNewFiles )
    {
        DirSnap.scan();
    }
    else
    {
        DirectorySnapshotDiff Diff = DirSnap.scan();

        if ( Diff.changed() )
        {
            FileInfoList::iterator it;

            DiffIterator( FilesCreated )
            {
                handleAction( ( *it ).Filepath, Actions::Add );
            }
        }
    }
}

DirWatcherGeneric::~DirWatcherGeneric()
{
    /// If the directory was deleted mark the files as deleted
    if ( Deleted )
    {
        DirectorySnapshotDiff Diff = DirSnap.scan();

        if ( !DirSnap.exists() )
        {
            FileInfoList::iterator it;

            DiffIterator( FilesDeleted )
            {
                handleAction( (*it).Filepath, Actions::Delete );
            }

            DiffIterator( DirsDeleted )
            {
                handleAction( (*it).Filepath, Actions::Delete );
            }
        }
    }

    DirWatchMap::iterator it = Directories.begin();

    for ( ; it != Directories.end(); it++ )
    {
        if ( Deleted )
        {
            /// If the directory was deleted, mark the flag for file deletion
            it->second->Deleted = true;
        }

        efSAFE_DELETE( it->second );
    }
}

void DirWatcherGeneric::resetDirectory( std::string directory )
{
    std::string dir( directory );

    /// Is this a recursive watch?
    if ( Watch->Directory != directory )
    {
        if ( !( directory.size() && ( directory.at(0) == FileSystem::getOSSlash() || directory.at( directory.size() - 1 ) == FileSystem::getOSSlash() ) ) )
        {
            /// Get the real directory
            if ( NULL != Parent )
            {
                FileSystem::dirAddSlashAtEnd(directory);

                dir = Parent->DirSnap.DirectoryInfo.Filepath + directory;
            }
            else
            {
                efDEBUG( "resetDirectory(): Parent is NULL. Fatal error." );
            }
        }
    }

    DirSnap.setDirectoryInfo( dir );
}

void DirWatcherGeneric::handleAction( const std::string &filename, unsigned long action, std::string oldFilename)
{
    Watch->Listener->handleFileAction( Watch->ID, DirSnap.DirectoryInfo.Filepath, FileSystem::fileNameFromPath( filename ), (Action)action, oldFilename );
}

void DirWatcherGeneric::addChilds( bool reportNewFiles )
{
    if ( Recursive )
    {
        /// Create the subdirectories watchers
        std::string dir;

        for ( FileInfoMap::iterator it = DirSnap.Files.begin(); it != DirSnap.Files.end(); it++ )
        {
            if ( it->second.isDirectory() && it->second.isReadable() && !FileSystem::isRemoteFS( it->second.Filepath ) )
            {
                /// Check if the directory is a symbolic link
                std::string curPath;
                std::string link( FileSystem::getLinkRealPath( it->second.Filepath, curPath ) );

                dir = it->first;

                if ( "" != link )
                {
                    /// Avoid adding symlinks directories if it's now enabled
                    if ( !Watch->WatcherImpl->mFileWatcher->followSymlinks() )
                    {
                        continue;
                    }

                    /// If it's a symlink check if the realpath exists as a watcher, or
                    /// if the path is outside the current dir
                    if ( Watch->WatcherImpl->pathInWatches( link ) || Watch->pathInWatches( link ) || !Watch->WatcherImpl->linkAllowed( curPath, link ) )
                    {
                        continue;
                    }
                    else
                    {
                        dir = link;
                    }
                }
                else
                {
                    if ( Watch->pathInWatches( dir ) || Watch->WatcherImpl->pathInWatches( dir ) )
                    {
                        continue;
                    }
                }

                if ( reportNewFiles )
                {
                    handleAction( dir, Actions::Add );
                }

                Directories[dir] = new DirWatcherGeneric( this, Watch, dir, Recursive, reportNewFiles );

                Directories[dir]->addChilds( reportNewFiles  );
            }
        }
    }
}

void DirWatcherGeneric::watch( bool reportOwnChange )
{
    DirectorySnapshotDiff Diff = DirSnap.scan();

    if ( reportOwnChange && Diff.DirChanged && NULL != Parent )
    {
        Watch->Listener->handleFileAction( Watch->ID, FileSystem::pathRemoveFileName( DirSnap.DirectoryInfo.Filepath ), FileSystem::fileNameFromPath( DirSnap.DirectoryInfo.Filepath ), Actions::Modified );
    }

    if ( Diff.changed() )
    {
        FileInfoList::iterator it;
        MovedList::iterator mit;

        /// Files
        DiffIterator( FilesCreated )
        {
            handleAction( (*it).Filepath, Actions::Add );
        }

        DiffIterator( FilesModified )
        {
            handleAction( (*it).Filepath, Actions::Modified );
        }

        DiffIterator( FilesDeleted )
        {
            handleAction( (*it).Filepath, Actions::Delete );
        }

        DiffMovedIterator( FilesMoved )
        {
            handleAction( (*mit).second.Filepath, Actions::Moved, (*mit).first );
        }

        /// Directories
        DiffIterator( DirsCreated )
        {
            createDirectory( (*it).Filepath );
        }

        DiffIterator( DirsModified )
        {
            handleAction( (*it).Filepath, Actions::Modified );
        }

        DiffIterator( DirsDeleted )
        {
            handleAction( (*it).Filepath, Actions::Delete );
            removeDirectory( (*it).Filepath );
        }

        DiffMovedIterator( DirsMoved )
        {
            handleAction( (*mit).second.Filepath, Actions::Moved, (*mit).first );
            moveDirectory( (*mit).first, (*mit).second.Filepath );
        }
    }

    /// Process the subdirectories looking for changes
    for ( DirWatchMap::iterator dit = Directories.begin(); dit != Directories.end(); dit++ )
    {
        /// Just watch
        dit->second->watch();
    }
}

void DirWatcherGeneric::watchDir( std::string &dir )
{
    DirWatcherGeneric * watcher = Watch->WatcherImpl->mFileWatcher->allowOutOfScopeLinks() ?
                                    findDirWatcher( dir ) :
                                    findDirWatcherFast( dir );

    if ( NULL != watcher )
    {
        watcher->watch( true );
    }
}

DirWatcherGeneric * DirWatcherGeneric::findDirWatcherFast( std::string dir )
{
    // remove the common base ( dir should always start with the same base as the watcher )
    efASSERT( !dir.empty() );
    efASSERT( dir.size() >= DirSnap.DirectoryInfo.Filepath.size() );
    efASSERT( DirSnap.DirectoryInfo.Filepath == dir.substr( 0, DirSnap.DirectoryInfo.Filepath.size() ) );

    if ( dir.size() >= DirSnap.DirectoryInfo.Filepath.size() )
    {
        dir = dir.substr( DirSnap.DirectoryInfo.Filepath.size() - 1 );
    }

    if ( dir.size() == 1 )
    {
        efASSERT( dir[0] == FileSystem::getOSSlash() );
        return this;
    }

    size_t level = 0;
    std::vector<std::string> dirv = String::split( dir, FileSystem::getOSSlash(), false );

    DirWatcherGeneric * watcher = this;

    while ( level < dirv.size() )
    {
        // search the dir level in the current watcher
        DirWatchMap::iterator it = watcher->Directories.find( dirv[ level ] );

        // found? continue with the next level
        if ( it != watcher->Directories.end() )
        {
            watcher = it->second;

            level++;
        }
        else
        {
            // couldn't found the folder level?
            // directory not watched
            return NULL;
        }
    }

    return watcher;
}

DirWatcherGeneric * DirWatcherGeneric::findDirWatcher( std::string dir )
{
    if ( DirSnap.DirectoryInfo.Filepath == dir )
    {
        return this;
    }
    else
    {
        DirWatcherGeneric * watcher = NULL;

        for ( DirWatchMap::iterator it = Directories.begin(); it != Directories.end(); it++ )
        {
            watcher = it->second->findDirWatcher( dir );

            if ( NULL != watcher )
            {
                return watcher;
            }
        }
    }

    return NULL;
}

DirWatcherGeneric * DirWatcherGeneric::createDirectory( std::string newdir )
{
    FileSystem::dirRemoveSlashAtEnd( newdir );
    newdir = FileSystem::fileNameFromPath( newdir );

    DirWatcherGeneric * dw = NULL;

    /// Check if the directory is a symbolic link
    std::string dir( DirSnap.DirectoryInfo.Filepath + newdir );

    FileSystem::dirAddSlashAtEnd( dir );

    FileInfo fi( dir );

    if ( !fi.isDirectory() || !fi.isReadable() || FileSystem::isRemoteFS( dir ) )
    {
        return NULL;
    }

    std::string curPath;
    std::string link( FileSystem::getLinkRealPath( dir, curPath ) );
    bool skip = false;

    if ( "" != link )
    {
        /// Avoid adding symlinks directories if it's now enabled
        if ( !Watch->WatcherImpl->mFileWatcher->followSymlinks() )
        {
            skip = true;
        }

        /// If it's a symlink check if the realpath exists as a watcher, or
        /// if the path is outside the current dir
        if ( Watch->WatcherImpl->pathInWatches( link ) || Watch->pathInWatches( link ) || !Watch->WatcherImpl->linkAllowed( curPath, link ) )
        {
            skip = true;
        }
        else
        {
            dir = link;
        }
    }
    else
    {
        if ( Watch->pathInWatches( dir ) || Watch->WatcherImpl->pathInWatches( dir ) )
        {
            skip = true;
        }
    }

    if ( !skip )
    {
        handleAction( newdir, Actions::Add );

        /// Creates the new directory watcher of the subfolder and check for new files
        dw = new DirWatcherGeneric( this, Watch, dir, Recursive );
        
        dw->addChilds();

        dw->watch();

        /// Add it to the list of directories
        Directories[ newdir ] = dw;
    }

    return dw;
}

void DirWatcherGeneric::removeDirectory( std::string dir )
{
    FileSystem::dirRemoveSlashAtEnd( dir );
    dir = FileSystem::fileNameFromPath( dir );

    DirWatcherGeneric * dw            = NULL;
    DirWatchMap::iterator dit;

    /// Folder deleted

    /// Search the folder, it should exists
    dit = Directories.find( dir );

    if ( dit != Directories.end() )
    {
        dw = dit->second;

        /// Flag it as deleted so it fire the event for every file inside deleted
        dw->Deleted = true;

        /// Delete the DirWatcherGeneric
        efSAFE_DELETE( dw );

        /// Remove the directory from the map
        Directories.erase( dit->first );
    }
}

void DirWatcherGeneric::moveDirectory( std::string oldDir, std::string newDir )
{
    FileSystem::dirRemoveSlashAtEnd( oldDir );
    oldDir = FileSystem::fileNameFromPath( oldDir );

    FileSystem::dirRemoveSlashAtEnd( newDir );
    newDir = FileSystem::fileNameFromPath( newDir );

    DirWatcherGeneric * dw            = NULL;
    DirWatchMap::iterator dit;

    /// Directory existed?
    dit = Directories.find( oldDir );

    if ( dit != Directories.end() )
    {
        dw = dit->second;

        /// Remove the directory from the map
        Directories.erase( dit->first );

        Directories[ newDir ] = dw;

        dw->resetDirectory( newDir );
    }
}

bool DirWatcherGeneric::pathInWatches( std::string path )
{
    if ( DirSnap.DirectoryInfo.Filepath == path )
    {
        return true;
    }

    for ( DirWatchMap::iterator it = Directories.begin(); it != Directories.end(); it++ )
    {
         if ( it->second->pathInWatches( path ) )
         {
             return true;
         }
    }

    return false;
}

} 
